import { ForEachEdge, ForEachFace } from './CascadeStandardLibrary.js';
import { ShapeToMesh } from './CascadeShapeToMesh.js';

let sceneShapes = [];
let externalShapes = {};
let currentShape = null;

export function loadFiles (fileName, fileText) {

	console.time('模型解析时间');
	sceneShapes = [];
	let lastImportedShape;
	if (fileName.toLowerCase().includes('.stl')) {
		lastImportedShape = importSTL(fileName, fileText);
	} else {
		lastImportedShape = importSTEPorIGES(fileName, fileText);
	}
	if (lastImportedShape) {
		sceneShapes.push(lastImportedShape);
	}
	if (lastImportedShape) {
		console.log('Imports complete, rendering shapes now...');
		const response = combineAndRenderShapes({ maxDeviation: 0.1 });
		console.timeEnd('模型解析时间');

		return response;
	}
}

/** This function parses the ASCII contents of a `.STEP` or `.IGES`
 * File as a Shape into the `externalShapes` dictionary. */
export function importSTEPorIGES (fileName, fileText) {

	// console.log('importSTEPorIGES fileText:\n', fileText);

	// Writes the uploaded file to Emscripten's Virtual Filesystem
	oc.FS.createDataFile('/', fileName, fileText, true, true);

	// Choose the correct OpenCascade file parsers to read the CAD file
	let reader;
	const tempFilename = fileName.toLowerCase();
	if (tempFilename.endsWith('.step') || tempFilename.endsWith('.stp')) {
		reader = new oc.STEPControl_Reader();
	} else if (tempFilename.endsWith('.iges') || tempFilename.endsWith('.igs')) {
		reader = new oc.IGESControl_Reader();
	} else { console.error('opencascade.js can\'t parse this extension! (yet)'); }

	const readResult = reader.ReadFile(fileName);            // Read the file
	if (Number(readResult) === 1) {
		console.log(fileName + ' loaded successfully!     Converting to OCC now...');
		reader.TransferRoots();                              // Translate all transferable roots to OpenCascade
		const stepShape = reader.OneShape();         // Obtain the results of translation in one OCCT shape

		// const externalShapes = {};
		// Add to the externalShapes dictionary
		externalShapes[fileName] = new oc.TopoDS_Shape(stepShape);
		externalShapes[fileName].hash = stringToHash(fileName);
		console.log('Shape Import complete! Use sceneShapes.push(externalShapes[\'' + fileName + '\']); to add it to the scene!');

		// Remove the file when we're done (otherwise we run into errors on reupload)
		oc.FS.unlink('/' + fileName);

		console.timeEnd('模型离散时间：');

		return externalShapes[fileName];
	} else {
		console.error('Something in OCCT went wrong trying to read ' + fileName);
		return null;
	}
}

/** This function parses the contents of an ASCII .STL File as a Shape
 * into the `externalShapes` dictionary. */
export function importSTL (fileName, fileText) {

	// console.log('fileText: \n', fileText);

	// Writes the uploaded file to Emscripten's Virtual Filesystem
	oc.FS.createDataFile('/', fileName, fileText, true, true);

	// Choose the correct OpenCascade file parsers to read the STL file
	const reader = new oc.StlAPI_Reader();
	const readShape = new oc.TopoDS_Shape();

	if (reader.Read(readShape, fileName)) {
		console.log(fileName + ' loaded successfully!     Converting to OCC now...');

		// Convert Shell to Solid as is expected
		const solidSTL = new oc.BRepBuilderAPI_MakeSolid();
		solidSTL.Add(new oc.TopoDS_Shape(readShape));

		// Add to the externalShapes dictionary
		externalShapes[fileName] = new oc.TopoDS_Shape(solidSTL.Solid());
		externalShapes[fileName].hash = stringToHash(fileName);
		console.log('Shape Import complete! Use sceneShapes.push(externalShapes[\'' + fileName + '\']); to see it!');

		// Remove the file when we're done (otherwise we run into errors on reupload)
		oc.FS.unlink('/' + fileName);

		console.timeEnd('模型离散时间：');

		return externalShapes[fileName];
	} else {
		console.log('Something in OCCT went wrong trying to read ' + fileName + '.  \n' +
			'Cascade Studio only imports small ASCII stl files for now!');
		return null;
	}
}

/** This function accumulates all the shapes in `sceneShapes` into the `TopoDS_Compound` `currentShape`
 * and converts it to a mesh (and a set of edges) with `ShapeToMesh()`, and sends it off to be rendered. */
function combineAndRenderShapes (payload) {
	// Initialize currentShape as an empty Compound Solid
	currentShape = new oc.TopoDS_Compound();
	const sceneBuilder = new oc.BRep_Builder();
	sceneBuilder.MakeCompound(currentShape);
	const fullShapeEdgeHashes = {};
	const fullShapeFaceHashes = {};
	// postMessage({ 'type': 'Progress', 'payload': { 'opNumber': opNumber++, 'opType': 'Combining Shapes' } });

	// If there are sceneShapes, iterate through them and add them to currentShape
	if (sceneShapes.length > 0) {
		for (let shapeInd = 0; shapeInd < sceneShapes.length; shapeInd++) {
			if (!sceneShapes[shapeInd] || !sceneShapes[shapeInd].IsNull || sceneShapes[shapeInd].IsNull()) {
				console.error('Null Shape detected in sceneShapes; skipping: ' + JSON.stringify(sceneShapes[shapeInd]));
				continue;
			}
			if (!sceneShapes[shapeInd].ShapeType) {
				console.error('Non-Shape detected in sceneShapes; ' +
					'are you sure it is a TopoDS_Shape and not something else that needs to be converted to one?');
				console.error(JSON.stringify(sceneShapes[shapeInd]));
				continue;
			}

			// Scan the edges and faces and add to the edge list
			Object.assign(fullShapeEdgeHashes, ForEachEdge(sceneShapes[shapeInd], (index, edge) => { }));
			ForEachFace(sceneShapes[shapeInd], (index, face) => {
				fullShapeFaceHashes[face.HashCode(100000000)] = index;
			});

			sceneBuilder.Add(currentShape, sceneShapes[shapeInd]);
		}

		// Use ShapeToMesh to output a set of triangulated faces and discretized edges to the 3D Viewport
		// postMessage({ 'type': 'Progress', 'payload': { 'opNumber': opNumber++, 'opType': 'Triangulating Faces' } });
		const facesAndEdges = ShapeToMesh(currentShape, payload.maxDeviation || 0.1, fullShapeEdgeHashes, fullShapeFaceHashes);

		// 释放缓存
		clearCache();

		// postMessage({ 'type': 'Progress', 'payload': { 'opNumber': opNumber, 'opType': '' } }); // Finish the progress
		return [facesAndEdges, payload.sceneOptions];
	} else {
		console.error('There were no scene shapes returned!');
	}
	// postMessage({ 'type': 'Progress', 'payload': { 'opNumber': opNumber, 'opType': '' } });
}

function stringToHash (string) {
	let hash = 0;
	if (string.length === 0) return hash;
	for (let i = 0; i < string.length; i++) {
		const char = string.charCodeAt(i);
		hash = ((hash << 5) - hash) + char;
		hash = hash & hash;
	}
	return hash;
}

function clearCache () {
	sceneShapes = [];
	externalShapes = {};
	currentShape = null;
}
